/*
 * File     : StructureByReferenceList.java
 * Created  : 11 Nov 2011
 *
 * Copyright © 2011 Matthew Wilson (mj. {my-surname} .uk {at} gmail.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.googlecode.dni.internal.structure;

import static com.googlecode.dni.internal.DniInternal.MEMORY_ACCESSOR;

import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;

import com.googlecode.dni.internal.DniInternal;
import com.googlecode.dni.internal.MemoryAccess;
import com.googlecode.dni.type.NativeObject;
import com.googlecode.dni.type.NativeObjectList;
import com.googlecode.dni.type.Pointer;
import com.googlecode.dni.type.nativetype.NativeTypes;
import com.googlecode.dni.type.structure.Structure;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;


/**
 * Implementation of {@link NativeObjectList} for {@link Structure}s held by
 * reference.
 *
 * @param <E>
 *            the element type
 *
 * @author Matthew Wilson
 */
final class StructureByReferenceList< E extends Structure > implements NativeObjectList< E >
{

    private final AbstractStructureFactory< E > factory;

    private final long pointer;

    private final int size;


    /**
     * Creates a new instance, allocating memory.
     *
     * @param factory
     *            the structure factory
     * @param size
     *            the number of elements in the list
     */
    StructureByReferenceList( final AbstractStructureFactory< E > factory,
                              final int size )
    {
        this.factory = factory;
        this.pointer = DniInternal.ALLOCATOR.allocate( size * (long) NativeTypes.POINTER_SIZE ).address();
        this.size = size;
    }

    /**
     * Creates a new instance from an existing pointer.
     *
     * @param factory
     *            the structure factory
     * @param pointer
     *            the pointer
     * @param size
     *            the number of elements in the list
     */
    StructureByReferenceList( final AbstractStructureFactory< E > factory,
                              final Pointer pointer,
                              final int size )
    {
        this.factory = factory;
        this.pointer = pointer.address();
        this.size = size;
    }


    /**
     * Creates from a region.
     *
     * @param parent
     *            the parent
     * @param fromIndex
     *            index of the first element (inclusive)
     * @param toIndex
     *            index of the last element (exclusive)
     */
    private StructureByReferenceList( final StructureByReferenceList< E > parent, final int fromIndex, final int toIndex )
    {
        if ( fromIndex < 0 || toIndex > parent.size || fromIndex > toIndex )
        {
            throw new IndexOutOfBoundsException();
        }

        this.factory = parent.factory;
        this.pointer = parent.pointer + ( fromIndex * (long) NativeTypes.POINTER_SIZE );
        this.size = toIndex - fromIndex;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Pointer pointer()
    {
        return Pointer.fromAddress( this.pointer );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isSameObject( final NativeObject other )
    {
        return other instanceof StructureByReferenceList && other.pointer().address() == this.pointer;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int size()
    {
        return this.size;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isEmpty()
    {
        return this.size == 0;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean contains( final Object o )
    {
        return indexOf( o ) >= 0;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Iterator< E > iterator()
    {
        return new StructureListIterator( 0 );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object[] toArray()
    {
        Object[] array = new Object[ this.size ];
        for ( int i = 0; i < array.length; i++ )
        {
            array[ i ] = get( i );
        }
        return array;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public < T > T[] toArray( final T[] a )
    {
        Object[] array = a;
        if ( a.length < this.size )
        {
            array = (Object[]) Array.newInstance( a.getClass().getComponentType(), this.size );
        }

        for ( int i = 0; i < array.length; i++ )
        {
            array[ i ] = get( i );
        }

        if ( array.length > this.size )
        {
            a[ this.size ] = null;
        }

        @SuppressWarnings( "unchecked" )
        T[] castArray = (T[]) array;
        return castArray;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean add( final E e )
    {
        throw new UnsupportedOperationException();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean remove( final Object o )
    {
        throw new UnsupportedOperationException();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean containsAll( final Collection< ? > c )
    {
        for ( final Object object : c )
        {
            if ( !contains( object ) )
            {
                return false;
            }
        }
        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean addAll( final Collection< ? extends E > c )
    {
        throw new UnsupportedOperationException();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean addAll( final int index, final Collection< ? extends E > c )
    {
        throw new UnsupportedOperationException();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean removeAll( final Collection< ? > c )
    {
        throw new UnsupportedOperationException();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean retainAll( final Collection< ? > c )
    {
        throw new UnsupportedOperationException();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void clear()
    {
        throw new UnsupportedOperationException();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public E get( final int index )
    {
        long structurePointer;
        if ( NativeTypes.POINTER_SIZE == 8 )
        {
            long pointerToRead = this.pointer + index * 8L;
            structurePointer = MEMORY_ACCESSOR.getLong( pointerToRead );
        }
        else
        {
            long pointerToRead = this.pointer + index * 4L;
            structurePointer = MEMORY_ACCESSOR.getInt( pointerToRead ) & 0xffffffffL;
        }

        return this.factory.fromPointer( structurePointer );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public E set( final int index, final E element )
    {

        long oldStructurePointer;
        if ( NativeTypes.POINTER_SIZE == 8 )
        {
            long pointerToUpdate = this.pointer + index * 8L;
            oldStructurePointer = MEMORY_ACCESSOR.getLong( pointerToUpdate );
            MEMORY_ACCESSOR.putLong( pointerToUpdate, Pointer.getAddress( element ) );
        }
        else
        {
            long pointerToUpdate = this.pointer + index * 4L;
            oldStructurePointer = MEMORY_ACCESSOR.getInt( pointerToUpdate ) & 0xffffffffL;
            MEMORY_ACCESSOR.putInt( pointerToUpdate, (int) Pointer.getAddress( element ) );
        }

        return this.factory.fromPointer( oldStructurePointer );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void add( final int index, final E element )
    {
        throw new UnsupportedOperationException();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public E remove( final int index )
    {
        throw new UnsupportedOperationException();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int indexOf( final Object o )
    {
        if ( o == null )
        {
            return indexOfPointer( 0L );
        }
        if ( !this.factory.getStructureType().isInstance( o ) )
        {
            return -1;
        }
        return indexOfPointer( ( (AbstractStructure) o ).pointer );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int lastIndexOf( final Object o )
    {
        if ( o == null )
        {
            return lastIndexOfPointer( 0L );
        }
        if ( !this.factory.getStructureType().isInstance( o ) )
        {
            return -1;
        }
        return lastIndexOfPointer( ( (AbstractStructure) o ).pointer );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ListIterator< E > listIterator()
    {
        return new StructureListIterator( 0 );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ListIterator< E > listIterator( final int index )
    {
        return new StructureListIterator( index );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public NativeObjectList< E > subList( final int fromIndex, final int toIndex )
    {
        return new StructureByReferenceList< E >( this, fromIndex, toIndex );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void free()
    {
        MemoryAccess.ALLOCATOR.free( pointer() );
    }

    /**
     * {@inheritDoc}
     */
    @SuppressFBWarnings( value = "NSE_NON_SYMMETRIC_EQUALS",
                         justification = "java.util.List defines its own rules" )
    @Override
    public boolean equals( final Object obj )
    {
        if ( !( obj instanceof List< ? >) )
        {
            return false;
        }
        if ( obj == this )
        {
            return true;
        }

        List< ? > other = (List< ? >) obj;
        if ( other.size() != this.size )
        {
            return false;
        }

        Iterator< ? > iterator = other.iterator();
        for ( int i = 0; i < this.size; i++ )
        {
            E element = get( i );
            Object otherElement = iterator.next();
            if ( element == null ? otherElement != null : !element.equals( otherElement ) )
            {
                return false;
            }
        }

        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode()
    {
        int result = 1;
        for ( int i = 0; i < this.size; i++ )
        {
            Object element = get( i );
            result = 31 * result + ( element == null ? 0 : element.hashCode() );
        }
        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString()
    {
        StringBuilder str = new StringBuilder( this.size * 32 + 32 );
        for ( int i = 0; i < this.size; i++ )
        {
            if ( i > 0 )
            {
                str.append( ',' );
            }
            str.append( get( i ) );
        }
        return str.append( ']' ).toString();
    }

    private int indexOfPointer( final long structurePointer )
    {
        if ( NativeTypes.POINTER_SIZE == 8 )
        {
            long ptr = this.pointer;
            for ( int i = 0; i < this.size; i++ )
            {
                if ( DniInternal.MEMORY_ACCESSOR.getLong( ptr ) == structurePointer )
                {
                    return i;
                }
                ptr += 8;
            }
        }
        else
        {
            int ptr = (int) this.pointer;
            int structurePointerAsInt = (int) structurePointer;
            for ( int i = 0; i < this.size; i++ )
            {
                if ( DniInternal.MEMORY_ACCESSOR.getInt( ptr & 0xffffffffL ) == structurePointerAsInt )
                {
                    return i;
                }
                ptr += 4;
            }
        }

        return -1;
    }

    private int lastIndexOfPointer( final long structurePointer )
    {
        if ( NativeTypes.POINTER_SIZE == 8 )
        {
            long ptr = this.pointer + ( this.size - 1 ) * 8L;
            for ( int i = this.size - 1; i >= 0; i-- )
            {
                if ( DniInternal.MEMORY_ACCESSOR.getLong( ptr ) == structurePointer )
                {
                    return i;
                }
                ptr -= 8;
            }
        }
        else
        {
            int ptr = (int) this.pointer + ( this.size - 1 ) * 4;
            int structurePointerAsInt = (int) structurePointer;
            for ( int i = this.size - 1; i >= 0; i-- )
            {
                if ( DniInternal.MEMORY_ACCESSOR.getInt( ptr & 0xffffffffL ) == structurePointerAsInt )
                {
                    return i;
                }
                ptr -= 4;
            }
        }

        return -1;
    }


    private final class StructureListIterator implements ListIterator< E >
    {
        private int index;
        private int lastReturnedIndex = -1;

        StructureListIterator( final int index )
        {
            this.index = index;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean hasNext()
        {
            return this.index < StructureByReferenceList.this.size;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public E next()
        {
            if ( !hasNext() )
            {
                throw new NoSuchElementException();
            }

            E element = getElement();
            this.index++;
            return element;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean hasPrevious()
        {
            return this.index > 0;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public E previous()
        {
            if ( !hasPrevious() )
            {
                throw new NoSuchElementException();
            }

            this.index--;
            E element = getElement();
            return element;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int nextIndex()
        {
            return this.index;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int previousIndex()
        {
            return this.index - 1;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void remove()
        {
            throw new UnsupportedOperationException();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void set( final E e )
        {
            if ( this.lastReturnedIndex < 0 )
            {
                throw new IllegalStateException();
            }

            StructureByReferenceList.this.set( this.index, e );
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void add( final E e )
        {
            throw new UnsupportedOperationException();
        }

        private E getElement()
        {
            E element = StructureByReferenceList.this.get( this.index );
            this.lastReturnedIndex = this.index;
            return element;
        }
    }

}
